﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Checkers
{
    internal class CheckerBoardGUI : TableLayoutPanel
    {
        internal CheckerBoard checkerBoard { get; set; }
        internal TuringTest P1 { get; set; }
        internal TuringTest P2 { get; set; }
        internal Point currentSelection { get; set; }
        internal Possible_Move[] availableMoves { get; set; }
        internal Form1 form { get; set; }
        internal int checkerBoxSize = 40;
        internal Boolean firstMove = false;
        internal int aiDepth = 3;

        internal CheckerBoardGUI(CheckerBoard checkerBoard)
        {
            this.checkerBoard = checkerBoard;

            //
            // Set configuration options with TableLayoutPanel
            this.Top = 20;
            this.Left = 20;
            this.AutoSize = true;
            this.BackColor = Color.DarkGray;

            //
            //
            this.UpdateCheckerBoardGUI();
        }

        internal CheckerBoardGUI(CheckerBoard checkerBoard, Form1 form)
            : this(checkerBoard)
        {
            this.form = form;
        }

        internal CheckerBoardGUI(CheckerBoard checkerBoard, TuringTest P1, TuringTest P2)
            : this(checkerBoard)
        {
            this.P1 = P1;
            this.P2 = P2;
        }

        internal void UpdatePlayerType(TuringTest P1, TuringTest P2)
        {
            this.P1 = P1;
            this.P2 = P2;
        }

        delegate void DUpdateCheckerBoardGUI();
        //
        // Modify the checkerboard object then call this method to update the checkerboard GUI
        internal void UpdateCheckerBoardGUI()
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new DUpdateCheckerBoardGUI(UpdateCheckerBoardGUI));
            }
            else
            {
                PictureBox[,] pictureBoxes = this.GetCheckerBoardGUI(this.checkerBoard);

                this.Controls.Clear();

                for (int i = 0; i < pictureBoxes.GetLength(0); ++i)
                {
                    for (int j = 0; j < pictureBoxes.GetLength(1); ++j)
                    {
                        this.Controls.Add(pictureBoxes[i, j], j, i);
                    }
                }
            }
        }

        private Task UpdateCheckerBoardGUIAsync()
        {
            return Task.Run(delegate
            {
                this.UpdateCheckerBoardGUI();
            });
        }

        internal Point GetCellPoint(PictureBox cell)
        {
            TableLayoutPanelCellPosition cellTablePosition = this.GetCellPosition(cell);

            return new Point(cellTablePosition.Row, cellTablePosition.Column);
        }

        async private void HandlePlayerSelections(object sender, EventArgs e)
        {
            if (this.currentSelection == null)
            {
                this.currentSelection = this.GetCellPoint((PictureBox)sender);

                if (this.checkerBoard.IsThisPlayerTurn(this.currentSelection))
                {
                    //
                    // Get and highlight the availableMoves
                    this.availableMoves = this.checkerBoard.GetAvailableMoves(this.currentSelection, !true);

                    if (this.availableMoves.Length == 0)
                    {
                        this.currentSelection = null;
                        this.availableMoves = null;
                        return;
                    }

                    this.HighlightAvailableMoves(availableMoves);
                    this.firstMove = true;
                }
                else
                {
                    this.currentSelection = null;
                    this.availableMoves = null;
                }
            }
            else
            {
                Point secondSelection = this.GetCellPoint((PictureBox)sender);

                //
                // If player choses same piece after picking it up, then we assume the player wants to cancel
                if (currentSelection.row == secondSelection.row &&
                    currentSelection.column == secondSelection.column)
                {
                    if (!this.firstMove)
                    {
                        this.ChangePlayerTurn();
                        this.form.UpdateForm();
                    }

                    this.UnHighlightMoves(this.availableMoves);
                    this.currentSelection = null;
                    this.availableMoves = null;
                    return;
                }
                else if (!this.IsValidMove(secondSelection))
                {
                    return;
                }
                //
                // If this is a valid move, we know the associated checkerboard, use that checkerboard
                bool playerMoveAgain = this.PerformMove(secondSelection);
                await this.UpdateCheckerBoardGUIAsync();

                if (playerMoveAgain)
                {
                    this.currentSelection = secondSelection;
                    Possible_Move[] secondSetOfAvailableMoves = this.checkerBoard.GetAvailableMoves(this.currentSelection, !false);

                    if (secondSetOfAvailableMoves == null || secondSetOfAvailableMoves.Length == 0)
                    {
                        EndPlayerTurn();
                        HandleAIMove();
                    }

                    this.HighlightAvailableMoves(secondSetOfAvailableMoves);
                    this.availableMoves = secondSetOfAvailableMoves;
                    this.firstMove = false;
                    return;
                }
                //
                // At the end player's turn, set currentSelection to null
                EndPlayerTurn();
                HandleAIMove();
            }
        }

        private void EndPlayerTurn()
        {
            //
            // At the end player's turn, set currentSelection to null
            this.currentSelection = null;
            this.availableMoves = null;

            this.ChangePlayerTurn();
            this.form.UpdateForm();
        }

        //
        // AI player
        async internal void HandleAIMove()
        {
            while (IsComputerPlayerTurn())
            {
                AI aiPlayer = new AI(aiDepth);

                Task<CheckerPiece[,]> aiMove = aiPlayer.PerformAIMoveAsync(this.checkerBoard, this.checkerBoard.currentPlayersTurn);
                Thread.Sleep(500);
                this.checkerBoard.checkerBoard = await aiMove;
                await this.UpdateCheckerBoardGUIAsync();
                EndPlayerTurn();
            }
        }

        private void ChangePlayerTurn()
        {
            if (this.checkerBoard.currentPlayersTurn == Player.Black)
                this.checkerBoard.currentPlayersTurn = Player.Red;
            else if (this.checkerBoard.currentPlayersTurn == Player.Red)
                this.checkerBoard.currentPlayersTurn = Player.Black;
            else
                throw new Exception("Invalid player's turn");
        }

        private bool PerformMove(Point secondSelection)
        {
            foreach (Possible_Move move in this.availableMoves)
            {
                if (move.piece.row == secondSelection.row &&
                    move.piece.column == secondSelection.column)
                {
                    this.checkerBoard.checkerBoard = move.board;
                    return move.proceed;
                }
            }

            return false;
        }

        private void UnHighlightMoves(Possible_Move[] availableMoves)
        {
            foreach (Possible_Move move in availableMoves)
            {
                this.UnHighlightPiece(move.piece);
            }
        }

        private void UnHighlightPiece(Point point)
        {
            this.Controls[point.column + point.row * CheckerBoard.checkerBoardSize].BackColor = Color.White;
        }

        private void HighlightAvailableMoves(Possible_Move[] availableMoves)
        {
            foreach (Possible_Move move in availableMoves)
            {
                //
                // Highlight all move's piece
                this.HighlightPiece(move.piece);
            }
        }

        private void HighlightPiece(Point point)
        {
            this.Controls[point.column + point.row * CheckerBoard.checkerBoardSize].BackColor = Color.Yellow;
        }

        private bool IsComputerPlayerTurn()
        {
            if ((this.checkerBoard.currentPlayersTurn == Player.Red && this.P1 == TuringTest.AI) ||
                (this.checkerBoard.currentPlayersTurn == Player.Black && this.P2 == TuringTest.AI))
                return true;
            else
                return false;
        }

        private bool IsValidMove(Point secondSelection)
        {
            if (this.availableMoves.Any(p => p.piece.row == secondSelection.row && p.piece.column == secondSelection.column))
                return true;
            else
                return false;
        }

        #region Painting the checkerboard
        private PictureBox[,] GetCheckerBoardGUI(CheckerBoard checkerBoard)
        {
            PictureBox[,] checkerBoardGUI = new PictureBox[checkerBoard.checkerBoard.GetLength(0), 
                checkerBoard.checkerBoard.GetLength(1)];

            for (int i = 0; i < checkerBoardGUI.GetLength(0); ++i)
            {
                for (int j = 0; j < checkerBoardGUI.GetLength(1); ++j)
                {
                    //
                    // Set properties for all cells
                    checkerBoardGUI[i, j] = new PictureBox();
                    checkerBoardGUI[i, j].Height = checkerBoxSize;
                    checkerBoardGUI[i, j].Width = checkerBoxSize;
                    checkerBoardGUI[i, j].Margin = new Padding(1);
                    //
                    // Set the method to handle cell clicks
                    checkerBoardGUI[i, j].Click += this.HandlePlayerSelections;

                    switch (checkerBoard.checkerBoard[i, j])
                    {
                        case CheckerPiece.BlackKing:
                            checkerBoardGUI[i, j].BackColor = Color.White;
                            checkerBoardGUI[i, j].Paint += this.DrawBlackKing;
                            break;
                        case CheckerPiece.BlackReg:
                            checkerBoardGUI[i, j].BackColor = Color.White;
                            checkerBoardGUI[i, j].Paint += this.DrawBlack;
                            break;
                        case CheckerPiece.RedKing:
                            checkerBoardGUI[i, j].BackColor = Color.White;
                            checkerBoardGUI[i, j].Paint += this.DrawRedKing;
                            break;
                        case CheckerPiece.RedReg:
                            checkerBoardGUI[i, j].BackColor = Color.White;
                            checkerBoardGUI[i, j].Paint += this.DrawRed;
                            break;
                        case CheckerPiece.Empty:
                            checkerBoardGUI[i, j].BackColor = Color.White;
                            break;
                        case CheckerPiece.Invalid:
                            checkerBoardGUI[i, j].BackColor = Color.Black;
                            break;
                        default:
                            throw new Exception("Invalid piece in the checkerboard");
                    }
                }
            }

            return checkerBoardGUI;
        }

        private void DrawRed(object sender, PaintEventArgs e)
        {
            this.DrawCheckerPiece(CheckerPiece.RedReg, (PictureBox)sender, e);
        }

        private void DrawRedKing(object sender, PaintEventArgs e)
        {
            this.DrawCheckerPiece(CheckerPiece.RedKing, (PictureBox)sender, e);
        }

        private void DrawBlack(object sender, PaintEventArgs e)
        {
            this.DrawCheckerPiece(CheckerPiece.BlackReg, (PictureBox)sender, e);
        }

        private void DrawBlackKing(object sender, PaintEventArgs e)
        {
            this.DrawCheckerPiece(CheckerPiece.BlackKing, (PictureBox)sender, e);
        }

        private void DrawCheckerPiece(CheckerPiece checkerPiece, PictureBox pictureBox, PaintEventArgs e)
        {
            string king = "K";
            PointF point = new PointF(12F, 11F);
            int fontSize = 12;

            e.Graphics.DrawEllipse(new Pen(Brushes.Pink), pictureBox.DisplayRectangle);
            e.Graphics.FillEllipse(Brushes.Pink, pictureBox.DisplayRectangle);

            switch (checkerPiece)
            {
                case CheckerPiece.BlackKing:
                    e.Graphics.FillEllipse(Brushes.Black, pictureBox.DisplayRectangle);
                    using (Font font = new System.Drawing.Font("Arial", fontSize))
                        e.Graphics.DrawString(king, font, Brushes.White, point);
                    break;
                case CheckerPiece.BlackReg:
                    e.Graphics.FillEllipse(Brushes.Black, pictureBox.DisplayRectangle);
                    break;
                case CheckerPiece.RedKing:
                    e.Graphics.FillEllipse(Brushes.Red, pictureBox.DisplayRectangle);
                    using (Font font = new System.Drawing.Font("Arial", fontSize))
                        e.Graphics.DrawString(king, font, Brushes.White, point);
                    break;
                case CheckerPiece.RedReg:
                    e.Graphics.FillEllipse(Brushes.Red, pictureBox.DisplayRectangle);
                    break;
                default:
                    throw new Exception("Invalid enumeration passed");
            }
        }
        #endregion
    }
}
